
// Copyright (C) 2009-2011 Luca Piccioni
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//  
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//  
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using Derm.OpenGL;

namespace Derm.Render
{
	/// <summary>
	/// Shader program.
	/// </summary>
	/// <remarks>
	/// <para>
	/// ShaderProgram automatically determine the text of the program using the
	/// shader objects attached (and their dependencies). The text shall be compiled
	/// and then linked all toghether to obtain an effective ShaderProgram instance.
	/// </para>
	/// <para>
	/// Once linked, a ShaderProgram has an active set of uniforms and an active set
	/// of inputs. Only the uniform symbols and input symbols with possible access are
	/// considered active.
	/// 
	/// Uniforms are shader program symbols which are constant during the shader program
	/// executions over a single primitive. They are preserved between different shader program
	/// executions, and they are lost if the shader program has been relinked.
	/// 
	/// Inputs are shader program symbols which are variable during the shader program
	/// execution. A shader program input is represented by a BufferObject instance; every
	/// active input in the shader program must be linked with a ArrayBufferObject, and every
	/// BufferObject must have the same items count <see cref="ArrayBufferObject.ItemCount"/>.
	/// </para>
	/// </remarks>
	public class ShaderProgram : RenderResource
	{
		#region Constructors

		/// <summary>
		/// Default constructor.
		/// </summary>
		static ShaderProgram()
		{
			// Initialize default shader library
			ShaderLibrary.Touch();
		}

		/// <summary>
		/// Construct a ShaderProgram.
		/// </summary>
		/// <param name="programName">
		/// A <see cref="System.String"/> that specifies the shader program name.
		/// </param>
		/// <exception cref="ArgumentException">
		/// This exception is thrown if the parameter <paramref name="programName"/> is not a valid name.
		/// </exception>
		public ShaderProgram(string programName)
		{
			if (String.IsNullOrEmpty(programName) == true)
				throw new ArgumentException("invalid", "programName");

			// Required for shader program database support
			mShaderProgramName = programName;
		}

		#endregion

		#region Program Linkage

		/// <summary>
		/// Attach a cached ShaderObject to this ShaderProgram.
		/// </summary>
		/// <param name="sObjectId">
		/// A <see cref="System.String"/> that specify the cached shader object name. This value usually correspond
		/// to a static field <i>LibraryId</i> declared by ShaderObject subclasses (however, this convention could
		/// be broken by some implementation).
		/// </param>
		/// <param name="stage">
		/// A <see cref="ShaderObject.Stage"/> that specify the stage of the cached shader object <paramref name="sObjectId"/>.
		/// </param>
		/// <exception cref="ArgumentNullException">
		/// Exception thrown if <paramref name="sObjectId"/> is null.
		/// </exception>
		public void AttachShader(string sObjectId, ShaderObject.Stage stage)
		{
			if (sObjectId == null)
				throw new ArgumentNullException("sObjectId");

			// Define cached shader object
			mProgramObjects.Add(new ProgramObject(sObjectId, stage));
		}

		/// <summary>
		/// Attach a ShaderObject to this ShaderProgram.
		/// </summary>
		/// <param name="sObject"></param>
		public void AttachShader(ShaderObject sObject)
		{
			AttachShader(sObject, null);
		}

		/// <summary>
		/// Attach a ShaderObject to this ShaderProgram, implementing an interface.
		/// </summary>
		/// <param name="sObject"></param>
		/// <param name="interface">
		/// </param>
		public void AttachShader(ShaderObject sObject, string @interface)
		{
			if (sObject == null)
				throw new ArgumentNullException("sObject");

			// Collect shader
			mProgramObjects.Add(new ProgramObject(sObject, @interface));
			// Force relink
			mLinked = false;
		}

		/// <summary>
		/// Detach an attached ShaderObject from this ShaderProgram.
		/// </summary>
		/// <param name="sObject"></param>
		public void DetachShader(ShaderObject sObject)
		{
			if (sObject == null)
				throw new ArgumentNullException("sObject");

			// Remove shader
			mProgramObjects.RemoveAll(delegate(ProgramObject programObject) { return (ReferenceEquals(sObject, programObject.Shader)); });
			// Unreference object
			sObject.Unref();
		}

		/// <summary>
		/// Check the attachment state of a ShaderObject on this ShaderProgram.
		/// </summary>
		/// <param name="sObject"></param>
		/// <returns>
		/// </returns>
		public bool IsAttachedShader(ShaderObject sObject)
		{
			if (sObject == null)
				throw new ArgumentNullException("sObject");

			return (mProgramObjects.Exists(delegate(ProgramObject programObject) { return (ReferenceEquals(sObject, programObject.Shader)); }));
		}

		/// <summary>
		/// Determine whether an shader interface would be linked into this program.
		/// </summary>
		/// <param name="interface"></param>
		/// <returns></returns>
		public bool HasInterface(string @interface)
		{
			return (mProgramObjects.Exists(delegate(ProgramObject programObject) { return (programObject.Interface == @interface); }));
		}

		/// <summary>
		/// Method overriden by derived classes for attaching default shader objects implementing required interfaces.
		/// </summary>
		protected virtual void LinkInterfaces()
		{
			
		}

		/// <summary>
		/// Link this ShaderProgram.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> used for linking this ShaderProgram.
		/// </param>
		/// <param name="cctx">
		/// A <see cref="ShaderCompilerContext"/> that specify additional compiler parameters.
		/// </param>
		/// <remarks>
		/// <para>
		/// Generate shader program source code, compile and link it. After a successfull
		/// link, obtain every information about active uniform and input variables.
		/// </para>
		/// <para>
		/// This routine generate the source code of each attached ShaderObject instance and
		/// compile it. This step is performed only if really required (tendentially every
		/// shader object is already compiled).
		/// </para>
		/// <para>
		/// After having compiled every attached shader object, it's performed the linkage between
		/// shader objects. After this process the ShaderProgram instance can be bound to issue
		/// rendering commands.
		/// </para>
		/// </remarks>
		/// <exception cref="InvalidOperationException">
		/// Exception thrown in the case this ShaderProgram is already linked.
		/// </exception>
		/// <exception cref="ShaderException">
		/// Exception throw in the case this ShaderProgram is not linkable.
		/// </exception>
		private void Link(RenderContext ctx, ShaderCompilerContext cctx)
		{
			if (ctx == null)
				throw new ArgumentNullException("ctx");
			if (cctx == null)
				throw new ArgumentNullException("cctx");
			if (IsLinked == true)
				throw new InvalidOperationException("program already linked");

			#region Compile and Attach Shader Objects

			// Attach default shaders implementing interfaces
			LinkInterfaces();

			// Collect (or create) cached shader objects
			foreach (ProgramObject programObject in mProgramObjects) {
				ShaderObject shaderObject = programObject.GetShaderObject(ctx, cctx);

				if (shaderObject.Exists(ctx) == false)
					shaderObject.Create(ctx, cctx);

				// Attach shader object
				Gl.AttachShader(ObjectName, shaderObject.ObjectName);
				RenderException.DebugCheckErrors();
			}

			#endregion

			#region Link Shader Program Objects

			int lStatus;

			sLog.Debug("Link shader program {0}", mShaderProgramName ?? "<Unnamed>");

			// Link shader program
			Gl.LinkProgram(ObjectName);
			RenderException.DebugCheckErrors();
			// Check for linking errors
			Gl.GetProgram(ObjectName, Gl.LINK_STATUS, out lStatus);
			RenderException.DebugCheckErrors();

			if (lStatus != Gl.TRUE) {
				const int MaxInfoLength = 4096;

				StringBuilder logInfo = new StringBuilder(MaxInfoLength);
				int logLength;

				// Obtain compilation log
				Gl.GetProgramInfoLog(ObjectName, MaxInfoLength, out logLength, logInfo);
				RenderException.DebugCheckErrors();

				// Stop link process
				StringBuilder sb = new StringBuilder(logInfo.Capacity);

				string[] compilerLogLines = logInfo.ToString().Split(new char[] {'\n'}, StringSplitOptions.RemoveEmptyEntries);
				foreach (string logLine in compilerLogLines)
					sb.AppendLine("  $ " + logLine);
				string sbString = sb.ToString();

				sLog.Error("failed to link shader program \"{0}\". The linker output:\n{1}", Identifier ?? "<Unnamed>", sbString);

				ShaderException shaderException =  new ShaderException("program link failure");
				shaderException.Data.Add("LinkerDebugMessages", sbString);
				throw shaderException;
			}
			// Set linked flag
			mLinked = true;

			#endregion

			#region Collect Active Program Uniforms and Inputs

			int uniformBufferSize, attributeBufferSize;
			int uniformCount;

			// Get active uniforms count
			Gl.GetProgram(ObjectName, Gl.ACTIVE_UNIFORMS, out uniformCount);
			RenderException.DebugCheckErrors();
			// Get uniforms maximum length for name
			Gl.GetProgram(ObjectName, Gl.ACTIVE_UNIFORM_MAX_LENGTH, out uniformBufferSize);
			RenderException.DebugCheckErrors();
			// Allocate string builder
			StringBuilder uNameBuilder = new StringBuilder(uniformBufferSize + 2);

			// Clear uniform mapping
			mUniformMap.Clear();
			mDefaultBlockUniformSlots = 0;

			// Collect uniform location mapping
			sLog.Debug("Shader program active uniforms:");
			for (uint i = 0; i < (uint)uniformCount; i++) {
				int uniformNameLength, uniformSize, uniformType;

				// Mono optimize StringBuilder capacity after P/Invoke... ensure enought room
				uNameBuilder.EnsureCapacity(uniformBufferSize);

				// Obtain active uniform informations
				Gl.GetActiveUniform(ObjectName, i, uniformBufferSize, out uniformNameLength, out uniformSize, out uniformType, uNameBuilder);
				RenderException.DebugCheckErrors();

				// Obtain active uniform location
				int uLocation = Gl.GetUniformLocation(ObjectName, uNameBuilder.ToString());
				RenderException.DebugCheckErrors();

				// Map active uniform
				UniformBinding uniformBinding = new UniformBinding();
				uniformBinding.Location = uLocation;
				uniformBinding.UniformType = (ShaderProgramUniformType) uniformType;
				mUniformMap[uNameBuilder.ToString()] = uniformBinding;

				sLog.Debug("\tUniform {0} (Type: {1}, Location: {2})", uNameBuilder.ToString(), uniformBinding.UniformType, uniformBinding.Location);

				// Keep track of used slot
				mDefaultBlockUniformSlots += GetUniformSlotCount(uniformBinding.UniformType);
			}

			sLog.Debug("Shader program active uniform slots: {0}", mDefaultBlockUniformSlots);

			// Get active inputs count
			int activeInputs;

			Gl.GetProgram(ObjectName, Gl.ACTIVE_ATTRIBUTES, out activeInputs);
			RenderException.DebugCheckErrors();
			// Get inputs maximum length for name
			Gl.GetProgram(ObjectName, Gl.ACTIVE_ATTRIBUTE_MAX_LENGTH, out attributeBufferSize);
			RenderException.DebugCheckErrors();
			// Allocate string builder
			StringBuilder aNameBuilder = new StringBuilder(attributeBufferSize);

			// Clear input mapping
			mAttributesMap.Clear();

			// Collect input location mapping
			sLog.Debug("Shader program active inputs:");
			for (uint i = 0; i < (uint)activeInputs; i++) {
				int aNameLength, aSize, aType;

				// Mono optimize StringBuilder capacity after P/Invoke... ensure enought room
				aNameBuilder.EnsureCapacity(attributeBufferSize);

				// Obtain active input informations
				Gl.GetActiveAttrib(ObjectName, i, attributeBufferSize, out aNameLength, out aSize, out aType, aNameBuilder);
				RenderException.DebugCheckErrors();

				// Obtain active input location
				int aLocation = Gl.GetAttribLocation(ObjectName, aNameBuilder.ToString());
				RenderException.DebugCheckErrors();

				// Map active input
				AttributeBinding attributeBinding = new AttributeBinding();
				attributeBinding.Location = (uint)aLocation;
				attributeBinding.Type = (VertexArrayType) aType;
				mAttributesMap[aNameBuilder.ToString()] = attributeBinding;

				sLog.Debug("\tInput {0} (Type: {1}, Location: {2})", aNameBuilder.ToString(), attributeBinding.Type, attributeBinding.Location);
			}

			#endregion
		}

		/// <summary>
		/// Get the number of slots occupied by an uniform type.
		/// </summary>
		/// <param name="uniformType"></param>
		/// <returns></returns>
		private static uint GetUniformSlotCount(ShaderProgramUniformType uniformType)
		{
			switch (uniformType) {
				case ShaderProgramUniformType.Float:
				case ShaderProgramUniformType.Int:
				case ShaderProgramUniformType.UInt:
				case ShaderProgramUniformType.Bool:
					return (1);
				case ShaderProgramUniformType.Vec2:
				case ShaderProgramUniformType.IntVec2:
				case ShaderProgramUniformType.UIntVec2:
				case ShaderProgramUniformType.BoolVec2:
					return (2);
				case ShaderProgramUniformType.Vec3:
				case ShaderProgramUniformType.IntVec3:
				case ShaderProgramUniformType.UIntVec3:
				case ShaderProgramUniformType.BoolVec3:
					return (3);
				case ShaderProgramUniformType.Vec4:
				case ShaderProgramUniformType.IntVec4:
				case ShaderProgramUniformType.UIntVec4:
				case ShaderProgramUniformType.BoolVec4:
					return (4);
				case ShaderProgramUniformType.Mat2x2:
					return (4);
				case ShaderProgramUniformType.Mat3x3:
					return (9);
				case ShaderProgramUniformType.Mat4x4:
					return (16);
				case ShaderProgramUniformType.Mat2x3:
				case ShaderProgramUniformType.Mat3x2:
					return (6);
				case ShaderProgramUniformType.Mat2x4:
				case ShaderProgramUniformType.Mat4x2:
					return (8);
				case ShaderProgramUniformType.Mat3x4:
				case ShaderProgramUniformType.Mat4x3:
					return (12);
				default:
					// Assume sampler type
					return (1);
				case ShaderProgramUniformType.Unknown:
					throw new ArgumentException("invalid type", "uniformType");
			}
		}

		/// <summary>
		/// Property to determine program linkage status.
		/// </summary>
		public bool IsLinked
		{
			get { return (mLinked); }
		}

		/// <summary>
		/// Validate this shader program.
		/// </summary>
		/// <exception cref="Exception">
		/// Throw an Exception in the case the validation has failed.
		/// </exception>
		[Conditional("DEBUG")]
		private void Validate()
		{
			int lStatus;

			if (IsLinked == false)
				throw new InvalidOperationException("not linked");

			// Request program validation
			Gl.ValidateProgram(ObjectName);
			RenderException.DebugCheckErrors();
			// Check for validation result
			Gl.GetProgram(ObjectName, Gl.VALIDATE_STATUS, out lStatus);
			RenderException.DebugCheckErrors();

			if (lStatus != Gl.TRUE) {
				const int MaxInfoLength = 4096;

				StringBuilder logInfo = new StringBuilder(256, MaxInfoLength);
				int logLength;

				// Obtain compilation log
				Gl.GetProgramInfoLog(ObjectName, MaxInfoLength, out logLength, logInfo);
				RenderException.DebugCheckErrors();

				// Stop link process
				StringBuilder sb = new StringBuilder(logInfo.Capacity);

				sb.AppendLine("shader program is not valid. Linker output:");
				string[] linkerLogLines = logInfo.ToString().Split(new char[] {'\n'}, StringSplitOptions.RemoveEmptyEntries);
				foreach (string logLine in linkerLogLines)
					sb.AppendLine("  > " + logLine);

				throw new InvalidOperationException(sb.ToString());
			}
		}

		/// <summary>
		/// Linked flag.
		/// </summary>
		private bool mLinked;

		/// <summary>
		/// Utility structure to collect cached ShaderObject information.
		/// </summary>
		private class ProgramObject
		{
			public ProgramObject(string identifier, ShaderObject.Stage stage)
			{
				Id = identifier;
				Stage = stage;
			}

			public ProgramObject(ShaderObject shader)
			{
				Shader = shader;
			}

			public ProgramObject(ShaderObject shader, string @interface) : this(shader)
			{
				Interface = @interface;
			}

			public ShaderObject GetShaderObject(RenderContext ctx, ShaderCompilerContext cctx)
			{
				if (Shader == null) {
					ShaderCacheService cacheService = ShaderCacheService.GetService(ctx);
					Shader = cacheService.CreateShaderObject(ctx, cctx, Id, Stage);
				}
				
				return (Shader);
			}

			/// <summary>
			/// Shader object identifier.
			/// </summary>
			public readonly string Id;

			/// <summary>
			/// Shader object stage.
			/// </summary>
			public readonly ShaderObject.Stage Stage;

			/// <summary>
			/// Cached shader object.
			/// </summary>
			public ShaderObject Shader;

			/// <summary>
			/// Implemented interface by the shader object.
			/// </summary>
			public readonly string Interface;
		}

		/// <summary>
		/// List of cached shader objects composing this shader program.
		/// </summary>
		private readonly List<ProgramObject> mProgramObjects = new List<ProgramObject>();

		#endregion

		#region Program Creation

		/// <summary>
		/// Create this ShaderProgram, specifying the compiler parameters.
		/// </summary>
		/// <param name="cctx">
		/// A <see cref="ShaderCompilerContext"/> that specify the compiler parameters used for compiling and
		/// linking this ShaderProgram.
		/// </param>
		public void Create(ShaderCompilerContext cctx)
		{
			if (cctx == null)
				throw new ArgumentNullException("cctx");

			// Cache compilation parameters (used by CreateObject)
			mCompilationParams = cctx;
		}

		/// <summary>
		/// Create this ShaderProgram, specifying the compiler parameters.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> used for creating this object.
		/// </param>
		/// <param name="cctx">
		/// A <see cref="ShaderCompilerContext"/> that specify the compiler parameters used for compiling and
		/// linking this ShaderProgram.
		/// </param>
		public void Create(RenderContext ctx, ShaderCompilerContext cctx)
		{
			if (ctx == null)
				throw new ArgumentNullException("ctx");
			if (cctx == null)
				throw new ArgumentNullException("cctx");

			// Cache compilation parameters (used by CreateObject)
			mCompilationParams = cctx;
			// Base implementation
			base.Create(ctx);
		}

		#endregion

		#region Program Binding

		/// <summary>
		/// Bind this ShaderProgram.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> used for binding.
		/// </param>
		public void Bind(RenderContext ctx)
		{
			int currentProgram;
			if (IsLinked == false)
				throw new InvalidOperationException("not linked");

			// UseProgram only if a different shader program is bound
			Gl.Get(Gl.CURRENT_PROGRAM, out currentProgram);
			RenderException.DebugCheckErrors();

			if (currentProgram != (int)ObjectName) {
				// Bind this program
				Gl.UseProgram(ObjectName);
				RenderException.DebugCheckErrors();
			}
		}

		/// <summary>
		/// Unbind this ShaderProgram.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> used for binding.
		/// </param>
		public void Unbind(RenderContext ctx)
		{
			// Unbind this program
			Gl.UseProgram(InvalidObjectName);
			RenderException.DebugCheckErrors();
		}

		#endregion

		#region Shader Program Attributes

		/// <summary>
		/// Collection of active attributes on this ShaderProgram.
		/// </summary>
		public ICollection<string> ActiveAttributes
		{
			get {
				return (mAttributesMap.Keys);
			}
		}

		/// <summary>
		/// Determine whether an attributes is active or not.
		/// </summary>
		/// <param name="uName">
		/// A <see cref="System.String"/> which specify the input name.
		/// </param>
		/// <returns>
		/// It returns true in the case the input named <paramref name="uName"/> is active.
		/// </returns>
		public bool IsActiveAttribute(string uName)
		{
			return (mAttributesMap.ContainsKey(uName));
		}

		/// <summary>
		/// Active attributes binding information.
		/// </summary>
		/// <param name="inputName"></param>
		/// <returns></returns>
		internal AttributeBinding GetActiveAttribute(string inputName)
		{
			return (mAttributesMap[inputName]);
		}

		/// <summary>
		/// Information about a shader program attribute.
		/// </summary>
		internal class AttributeBinding
		{
			/// <summary>
			/// Attribute location.
			/// </summary>
			public uint Location;

			/// <summary>
			/// The type of the shader program attribute.
			/// </summary>
			public VertexArrayType Type;
		}

		/// <summary>
		/// Map active uniform location with uniform name.
		/// </summary>
		private readonly Dictionary<string, AttributeBinding> mAttributesMap = new Dictionary<string, AttributeBinding>();

		#endregion

		#region Program Attributes Semantic

		/// <summary>
		/// 
		/// </summary>
		/// <param name="attributeName"></param>
		/// <returns></returns>
		public string GetAttributeSemantic(string attributeName)
		{
			string semantic;

			if (attributeName == null)
				throw new ArgumentNullException("attributeName");

			// Extract array name
			Match arrayMatch = Regex.Match(attributeName, @"(?<AttributeName>\w+)\[(?<AttributeIndex>\d+)\]");
			if (arrayMatch.Success)
				attributeName = arrayMatch.Groups["AttributeName"].Value;

			if (mAttributeSemantic.TryGetValue(attributeName, out semantic)) {
				if (arrayMatch.Success)
					semantic = String.Format("{0}[{1}]", semantic, arrayMatch.Groups["AttributeIndex"].Value);

				return (semantic);
			}

			return (null);
		}

		/// <summary>
		/// Define a shader program attribute semantic.
		/// </summary>
		/// <param name="attributeName">
		/// A <see cref="System.String"/> that specify the attribute name. This value doesn't have to match with the actual
		/// shader program attributes.
		/// </param>
		/// <param name="semantic">
		/// A <see cref="System.String"/> that specify the attribute semantic. It can be any value meaninfull for the application.
		/// </param>
		public void SetAttributeSemantic(string attributeName, string semantic)
		{
			if (attributeName == null)
				throw new ArgumentNullException("attributeName");
			if (semantic == null)
				throw new ArgumentNullException("semantic");

			mAttributeSemantic[attributeName] = semantic;
		}

		/// <summary>
		/// Remove a specific attribute semantic.
		/// </summary>
		/// <param name="attributeName">
		/// A <see cref="System.String"/> that specify the attribute name. This value doesn't have to match with the actual
		/// shader program attributes.
		/// </param>
		public void ResetAttributeSemantic(string attributeName)
		{
			if (attributeName == null)
				throw new ArgumentNullException("attributeName");

			mAttributeSemantic.Remove(attributeName);
		}

		/// <summary>
		/// Remove all defined attribute semantics.
		/// </summary>
		public void ClearAttributeSemantic()
		{
			mAttributeSemantic.Clear();
		}

		/// <summary>
		/// Map between program attribute names and attribute semantic.
		/// </summary>
		private readonly Dictionary<string, string> mAttributeSemantic = new Dictionary<string, string>();

		#endregion

		#region Shader Program Uniforms

		/// <summary>
		/// The number of slots used for storing uniform data.
		/// </summary>
		public uint UniformSlots { get { return (mDefaultBlockUniformSlots); } }

		/// <summary>
		/// Collection of active uniforms on this ShaderProgram.
		/// </summary>
		public ICollection<string> ActiveUniforms
		{
			get {
				return (mUniformMap.Keys);
			}
		}

		/// <summary>
		/// Determine whether an uniform is active or not.
		/// </summary>
		/// <param name="uName">
		/// A <see cref="System.String"/> which specify the uniform name.
		/// </param>
		/// <returns>
		/// </returns>
		public bool IsActiveUniform(string uName)
		{
			return (mUniformMap.ContainsKey(uName));
		}

		/// <summary>
		/// Request uniform variable location.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> used for getting uniform location.
		/// </param>
		/// <param name="uniformName">
		/// A <see cref="System.String"/> of the uniform variable used.
		/// </param>
		/// <returns></returns>
		private int GetUniformLocation(RenderContext ctx, string uniformName)
		{
			UniformBinding uniformBinding;

			if (ctx == null)
				throw new ArgumentNullException("ctx");
			if (uniformName == null)
				throw new ArgumentNullException("uniformName");

			// If not already mapped, probably is an array, structure or any combination
			// of these

			CheckProgramBinding(ctx);

			if (mUniformMap.TryGetValue(uniformName, out uniformBinding) == false) {
				// Obtain location from program
				int uniformLocation = Gl.GetUniformLocation(ObjectName, uniformName);
				RenderException.DebugCheckErrors();
				// Cache uniform location (probably accessed in the future), but not the type
				uniformBinding = new UniformBinding();
				uniformBinding.Location = uniformLocation;
				mUniformMap[uniformName] = uniformBinding;
			}

			return (uniformBinding.Location);
		}

		/// <summary>
		/// Set uniform state variable (any known object type).
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> used for operations.
		/// </param>
		/// <param name="uName">
		/// A <see cref="System.String"/> that specifies the variable name in the shader source.
		/// </param>
		/// <param name="value">
		/// A <see cref="System.Object"/> holding the uniform variabile data.
		/// </param>
		/// <remarks>
		/// <para>
		/// The following objects are recognized:
		/// - int
		/// - Vertex3f
		/// - Vertex4f
		/// - Matrix4x4
		/// 
		/// The following object types are not supported by this function:
		/// - Texture
		/// </para>
		/// </remarks>
		/// <exception cref="ArgumentNullException">
		/// This exception is thrown if the parameter <paramref name="ctx"/> is null.
		/// </exception>
		/// <exception cref="ArgumentNullException">
		/// This exception is thrown if the parameter <paramref name="uName"/> is null.
		/// </exception>
		public void SetUniform(RenderContext ctx, string uName, object value)
		{
			if (ctx == null)
				throw new ArgumentNullException("ctx");
			if (value == null)
				throw new ArgumentNullException("value");
		
			MethodInfo setUniformMethod;

			if (sSetUniformMethods.TryGetValue(value.GetType(), out setUniformMethod) == false) {
				setUniformMethod = typeof(ShaderProgram).GetMethod("SetUniform", new Type[] { typeof(RenderContext), typeof(string), value.GetType() });
				sSetUniformMethods[value.GetType()] = setUniformMethod;
			}

			if (setUniformMethod != null)
				setUniformMethod.Invoke(this, new object[] {ctx, uName, value});
			else
				throw new NotSupportedException(value.GetType() + " is not supported");
		}

		/// <summary>
		/// Methods used for setting uniform values.
		/// </summary>
		private static readonly Dictionary<Type, MethodInfo> sSetUniformMethods = new Dictionary<Type, MethodInfo>();

		/// <summary>
		/// Set uniform state variable (floating-point variable).
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> used for operations.
		/// </param>
		/// <param name="uName">
		/// A <see cref="System.String"/> that specifies the variable name in the shader source.
		/// </param>
		/// <param name="v">
		/// A <see cref="Single"/> holding the uniform variabile data.
		/// </param>
		public void SetUniform(RenderContext ctx, string uName, float v)
		{
			int uLocation = GetUniformLocation(ctx, uName);

			if (uLocation < 0)
				throw new InvalidOperationException(String.Format("no active uniform named {0}", uName));

			CheckProgramBinding(ctx);
			CheckUniform(ctx, uName, uLocation, Gl.FLOAT);

			// Set uniform value
			Gl.Uniform1(uLocation, v);
			RenderException.DebugCheckErrors();
		}

		/// <summary>
		/// Get uniform value (floating-point variable).
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> used for operations.
		/// </param>
		/// <param name="uName">
		/// A <see cref="System.String"/> that specifies the variable name in the shader source.
		/// </param>
		/// <param name="v">
		/// A <see cref="Single"/> holding the returned uniform variabile data.
		/// </param>
		public void GetUniform(RenderContext ctx, string uName, out float v)
		{
			int uLocation = GetUniformLocation(ctx, uName);

			if (uLocation < 0)
				throw new InvalidOperationException(String.Format("no active uniform named {0}", uName));

			CheckUniform(ctx, uName, uLocation, Gl.FLOAT);

			float[] value = new float[1];

			// Set uniform value
			Gl.GetUniform(ObjectName, uLocation, value);
			RenderException.DebugCheckErrors();

			v = value[0];
		}

		/// <summary>
		/// Set uniform state variable (integer variable).
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> used for operations.
		/// </param>
		/// <param name="uName">
		/// A <see cref="System.String"/> that specifies the variable name in the shader source.
		/// </param>
		/// <param name="v">
		/// A <see cref="Vertex3f"/> holding the uniform variabile data.
		/// </param>
		public void SetUniform(RenderContext ctx, string uName, int v)
		{
			int uLocation = GetUniformLocation(ctx, uName);

			if (uLocation < 0)
				throw new InvalidOperationException(String.Format("no active uniform named {0}", uName));

			CheckProgramBinding(ctx);
			CheckUniform(ctx, uName, uLocation, Gl.INT);

			// Set uniform value
			Gl.Uniform1(uLocation, v);
			RenderException.DebugCheckErrors();
		}

		/// <summary>
		/// Set uniform state variable (integer variable).
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> used for operations.
		/// </param>
		/// <param name="uName">
		/// A <see cref="System.String"/> that specifies the variable name in the shader source.
		/// </param>
		/// <param name="v">
		/// A <see cref="System.UInt32"/> holding the uniform variabile data.
		/// </param>
		public void SetUniform(RenderContext ctx, string uName, uint v)
		{
			int uLocation = GetUniformLocation(ctx, uName);

			if (uLocation < 0)
				throw new InvalidOperationException(String.Format("no active uniform named {0}", uName));

			CheckProgramBinding(ctx);
			CheckUniform(ctx, uName, uLocation, Gl.UNSIGNED_INT);

			// Set uniform value
			Gl.Uniform1(uLocation, v);
			RenderException.DebugCheckErrors();
		}

		/// <summary>
		/// Set uniform state variable (integer variable).
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> used for operations.
		/// </param>
		/// <param name="uName">
		/// A <see cref="System.String"/> that specifies the variable name in the shader source.
		/// </param>
		/// <param name="v">
		/// A <see cref="System.UInt32"/> holding the uniform variabile data.
		/// </param>
		public void SetUniform(RenderContext ctx, string uName, bool v)
		{
			int uLocation = GetUniformLocation(ctx, uName);

			if (uLocation < 0)
				throw new InvalidOperationException(String.Format("no active uniform named {0}", uName));

			CheckProgramBinding(ctx);
			CheckUniform(ctx, uName, uLocation, Gl.BOOL);

			// Set uniform value
			Gl.Uniform1(uLocation, v ? 1 : 0);
			RenderException.DebugCheckErrors();
		}

		/// <summary>
		/// Set uniform state variable (vec2 variable).
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> used for operations.
		/// </param>
		/// <param name="uName">
		/// A <see cref="System.String"/> that specifies the variable name in the shader source.
		/// </param>
		/// <param name="x">
		/// A <see cref="System.Single"/> holding the uniform variabile data (first component).
		/// </param>
		/// <param name="y">
		/// A <see cref="System.Single"/> holding the uniform variabile data (second component).
		/// </param>
		public void SetUniform(RenderContext ctx, string uName, float x, float y)
		{
			int uLocation = GetUniformLocation(ctx, uName);

			if (uLocation < 0)
				throw new InvalidOperationException(String.Format("no active uniform named {0}", uName));

			CheckProgramBinding(ctx);
			CheckUniform(ctx, uName, uLocation, Gl.FLOAT_VEC2);

			// Set uniform value
			Gl.Uniform2(uLocation, x, y);
			RenderException.DebugCheckErrors();
		}

		/// <summary>
		/// Set uniform state variable (vec2 variable).
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> used for operations.
		/// </param>
		/// <param name="uName">
		/// A <see cref="System.String"/> that specifies the variable name in the shader source.
		/// </param>
		/// <param name="v">
		/// A <see cref="Vertex2f"/> holding the uniform variabile data.
		/// </param>
		public void SetUniform(RenderContext ctx, string uName, Vertex2f v)
		{
			int uLocation = GetUniformLocation(ctx, uName);

			if (uLocation < 0)
				throw new InvalidOperationException(String.Format("no active uniform named {0}", uName));

			CheckProgramBinding(ctx);
			CheckUniform(ctx, uName, uLocation, Gl.FLOAT_VEC2);

			// Set uniform value
			Gl.Uniform2(uLocation, v.x, v.y);
			RenderException.DebugCheckErrors();

			// Validate program
			Validate();
		}

		/// <summary>
		/// Set uniform state variable (vec3 variable).
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> used for operations.
		/// </param>
		/// <param name="uName">
		/// A <see cref="System.String"/> that specifies the variable name in the shader source.
		/// </param>
		/// <param name="v">
		/// A <see cref="Vertex3f"/> holding the uniform variabile data.
		/// </param>
		public void SetUniform(RenderContext ctx, string uName, Vertex3f v)
		{
			int uLocation = GetUniformLocation(ctx, uName);

			if (uLocation < 0)
				throw new InvalidOperationException(String.Format("no active uniform named {0}", uName));

			CheckProgramBinding(ctx);
			CheckUniform(ctx, uName, uLocation, Gl.FLOAT_VEC3);

			// Set uniform value
			Gl.Uniform3(uLocation, 1, (float[])v);
			RenderException.DebugCheckErrors();
		}

		/// <summary>
		/// Set uniform state variable (vec4 variable).
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> used for operations.
		/// </param>
		/// <param name="uName">
		/// A <see cref="System.String"/> that specifies the variable name in the shader source.
		/// </param>
		/// <param name="v">
		/// A <see cref="Vertex3f"/> holding the uniform variabile data.
		/// </param>
		public void SetUniform(RenderContext ctx, string uName, Vertex4f v)
		{
			int uLocation = GetUniformLocation(ctx, uName);

			if (uLocation < 0)
				throw new InvalidOperationException(String.Format("no active uniform named {0}", uName));

			CheckProgramBinding(ctx);
			CheckUniform(ctx, uName, uLocation, Gl.FLOAT_VEC4);

			// Set uniform value
			Gl.Uniform4(uLocation, 1, v);
			RenderException.DebugCheckErrors();
		}

		/// <summary>
		/// Set uniform state variable (mat3 variable).
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> used for operations.
		/// </param>
		/// <param name="uName">
		/// A <see cref="System.String"/> that specifies the variable name in the shader source.
		/// </param>
		/// <param name="m">
		/// A <see cref="Matrix3x3"/> holding the uniform variabile data.
		/// </param>
		public void SetUniform(RenderContext ctx, string uName, Matrix3x3 m)
		{
			int uLocation = GetUniformLocation(ctx, uName);

			if (uLocation < 0)
				throw new InvalidOperationException(String.Format("no active uniform named {0}", uName));

			CheckProgramBinding(ctx);
			CheckUniform(ctx, uName, uLocation, Gl.FLOAT_MAT3);

			// Set uniform value
			unsafe {
				fixed (float* matrix = m.MatrixBuffer) {
					Gl.UniformMatrix3(uLocation, 1, false, matrix);
					RenderException.DebugCheckErrors();
				}
			}
		}

		/// <summary>
		/// Set uniform state variable (mat4 variable).
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> used for operations.
		/// </param>
		/// <param name="uName">
		/// A <see cref="System.String"/> that specifies the variable name in the shader source.
		/// </param>
		/// <param name="m">
		/// A <see cref="Matrix4x4"/> holding the uniform variabile data.
		/// </param>
		public void SetUniform(RenderContext ctx, string uName, Matrix4x4 m)
		{
			int uLocation = GetUniformLocation(ctx, uName);

			if (uLocation < 0)
				throw new InvalidOperationException(String.Format("no active uniform named {0}", uName));

			CheckProgramBinding(ctx);
			CheckUniform(ctx, uName, uLocation, Gl.FLOAT_MAT4);

			// Set uniform value
			unsafe {
				fixed (float* matrix = m.MatrixBuffer) {
					Gl.UniformMatrix4(uLocation, 1, false, matrix);
					RenderException.DebugCheckErrors();
				}
			}
		}

		/// <summary>
		/// Set uniform state variable (vec4 variable).
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> used for operations.
		/// </param>
		/// <param name="uName">
		/// A <see cref="System.String"/> that specifies the variable name in the shader source.
		/// </param>
		/// <param name="v">
		/// A <see cref="Vertex3f"/> holding the uniform variabile data.
		/// </param>
		public void SetUniform(RenderContext ctx, string uName, ColorRGBAF v)
		{
			int uLocation = GetUniformLocation(ctx, uName);

			if (uLocation < 0)
				throw new InvalidOperationException(String.Format("no active uniform named {0}", uName));

			CheckProgramBinding(ctx);
			CheckUniform(ctx, uName, uLocation, Gl.FLOAT_VEC4);

			// Set uniform value
			Gl.Uniform4(uLocation, 1, (float[])v);
			RenderException.DebugCheckErrors();
		}

		public void SetUniform(RenderContext ctx, string uName, Texture tex)
		{
			SetUniform(ctx, uName, tex, mTexActiveUnit++);
		}

		/// <summary>
		/// Set uniform state variable (sampler1D, sampler2D, sampler3D, samplerCube variable).
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> used for operations.
		/// </param>
		/// <param name="uName">
		/// A <see cref="System.String"/> that specifies the variable name in the shader source.
		/// </param>
		/// <param name="tex">
		/// A <see cref="Texture"/> holding the uniform variabile data (the texture name).
		/// </param>
		/// <param name="texUnit">
		/// A <see cref="System.Int32"/> that specifies the texture unit processing the texture <paramref name="tex"/>.
		/// </param>
		public void SetUniform(RenderContext ctx, string uName, Texture tex, uint texUnit)
		{
			int uLocation = GetUniformLocation(ctx, uName);

			if (tex == null)
				throw new ArgumentNullException("tex");
			if (tex.Exists(ctx) == false)
				throw new ArgumentException("not exists", "tex");
			if (uLocation < 0)
				throw new InvalidOperationException(String.Format("no active uniform named {0}", uName));

			CheckProgramBinding(ctx);
			CheckUniform(ctx, uName, uLocation, tex.SamplerType);

			// Activate texture unit
			Gl.ActiveTexture(Gl.TEXTURE0+(int)texUnit);
			RenderException.DebugCheckErrors();
			// Bind texture (on active texture unit)
			tex.Bind(ctx);
			// Apply texture parameters
			tex.ApplyParameters(ctx);

			// Set uniform value (sampler)
			// Cast to Int32 since the sampler type can be set only with glUniform2i
			Gl.Uniform1(uLocation, (int)texUnit);
			RenderException.DebugCheckErrors();

			// Validate program
			Validate();
		}

		/// <summary>
		/// 
		/// </summary>
		public void ResetTextureUnits()
		{
			mTexActiveUnit = 0;
		}

		/// <summary>
		/// The current active texture unit.
		/// </summary>
		private uint mTexActiveUnit;

		/// <summary>
		/// Check uniform variable coherence.
		/// </summary>
		/// <param name="ctx">
		/// </param>
		/// <param name="uName"></param>
		/// <param name="uReqLocation"></param>
		/// <param name="uReqType"></param>
		/// <returns></returns>
		[Conditional("DEBUG")]
		private void CheckUniform(RenderContext ctx, string uName, int uReqLocation, int uReqType)
		{
			int uniformBufferSize, uniformNameLength, uniformSize, uniformType;

			// Check uniform location coherence
			int loc = Gl.GetUniformLocation(ObjectName, uName);
			RenderException.DebugCheckErrors();

			/* ! Check: required location shall correspond */
			if (uReqLocation != loc)
				throw new InvalidOperationException("uniform location mismatch");

			// Check uniform type coherence
			Gl.GetProgram(ObjectName, Gl.ACTIVE_UNIFORM_MAX_LENGTH, out uniformBufferSize);
			RenderException.DebugCheckErrors();
			// Allocate string builder
			StringBuilder nameBuilder = new StringBuilder(uniformBufferSize);
			// Obtain uniform infomations
			Gl.GetActiveUniform(ObjectName, (uint)loc, uniformBufferSize, out uniformNameLength, out uniformSize, out uniformType, nameBuilder);
			RenderException.DebugCheckErrors();

			/* ! Check: required uniform type shall correspond */
			if (uniformType != uReqType)
				throw new InvalidOperationException("uniform type mismatch");
		}

		/// <summary>
		/// Check whether this ShaderProgram is bound.
		/// </summary>
		/// <param name="ctx">
		/// 
		/// </param>
		[Conditional("DEBUG")]
		private void CheckProgramBinding(RenderContext ctx)
		{
			int program;

			Gl.Get(Gl.CURRENT_PROGRAM, out program);
			RenderException.DebugCheckErrors();

			if (program != ObjectName)
				throw new InvalidOperationException("no shader program bound");
		}

		/// <summary>
		/// Shader program uniform binding.
		/// </summary>
		private class UniformBinding
		{
			/// <summary>
			/// Uniform location.
			/// </summary>
			public int Location;

			/// <summary>
			/// Uniform type.
			/// </summary>
			public ShaderProgramUniformType UniformType = ShaderProgramUniformType.Unknown;
		}

		/// <summary>
		/// Map active uniform location with uniform name.
		/// </summary>
		private readonly Dictionary<string,UniformBinding> mUniformMap = new Dictionary<string,UniformBinding>();

		/// <summary>
		/// Uniform slots used by the default block of this shader program.
		/// </summary>
		private uint mDefaultBlockUniformSlots;

		#endregion

		#region Shader Programs Library Support

		/// <summary>
		/// Determine an unique identifier that specify the linked shader program.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> used to determine the shader object name space.
		/// </param>
		/// <param name="cctx">
		/// A <see cref="ShaderCompilerContext"/> determining the compiler parameteres.
		/// </param>
		/// <param name="libraryId">
		/// A <see cref="System.String"/> that identifies the shader object in library.
		/// </param>
		/// <returns>
		/// It returns a string that identify the a shader program classified with <paramref name="libraryId"/> compiled using
		/// <paramref name="ctx"/>, by specifying <paramref name="cctx"/> as compiled parameters.
		/// </returns>
		internal static string ComputeLibraryHash(RenderContext ctx, ShaderCompilerContext cctx, string libraryId)
		{
			StringBuilder hashMessage = new StringBuilder();

			// Take into account the shader program name
			hashMessage.Append(libraryId);
			// Take into account the shader version
			hashMessage.Append(cctx.ShaderVersion);
			// Take into account the shader program compilation symbols
			foreach (string symbol in cctx.Defines)
				hashMessage.AppendFormat("{0}", symbol);
			// Take into account the shader program include paths
			foreach (string includePath in cctx.Includes)
				hashMessage.AppendFormat("{0}", includePath);

			// Hash all information
			byte[] hashBytes;
			using (System.Security.Cryptography.HashAlgorithm hash = System.Security.Cryptography.HashAlgorithm.Create("SHA256")) {
				hashBytes = hash.ComputeHash(Encoding.ASCII.GetBytes(hashMessage.ToString()));
			}

			// Convert has to string
			return (Convert.ToBase64String(hashBytes));
		}

		/// <summary>
		/// This ShaderProgram name.
		/// </summary>
		private string mShaderProgramName;

		#endregion

		#region Logging

		/// <summary>
		/// Logger of this class.
		/// </summary>
		private static readonly ILogger sLog = Log.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

		#endregion

		#region RenderResource Overrides

		/// <summary>
		/// Shader program object class.
		/// </summary>
		internal static readonly Guid ShaderProgramObjectClass = new Guid("63E57E43-9DD1-46E3-8EA0-DE87836818D7");

		/// <summary>
		/// Shader program object class.
		/// </summary>
		public override Guid ObjectClass { get { return (ShaderProgramObjectClass); } }

		/// <summary>
		/// Determine whether this ShaderProgram really exists for a specific context.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> that would have created (or a sharing one) the object. This context shall be current to
		/// the calling thread.
		/// </param>
		/// <returns>
		/// It returns a boolean value indicating whether this ShaderProgram exists in the object space of <paramref name="ctx"/>.
		/// </returns>
		/// <remarks>
		/// <para>
		/// The object existence is done by checking a valid object by its name <see cref="IRenderResource.ObjectName"/>. This routine will test whether
		/// <paramref name="ctx"/> has created this ShaderProgram (or is sharing with the creator).
		/// </para>
		/// </remarks>
		/// <exception cref="ArgumentNullException">
		/// Exception thrown if <paramref name="ctx"/> is null.
		/// </exception>
		/// <exception cref="ArgumentException">
		/// Exception thrown if <paramref name="ctx"/> is not current to the calling thread.
		/// </exception>
		public override bool Exists(RenderContext ctx)
		{
			if (ctx == null)
				throw new ArgumentNullException("ctx");
			if (ctx.IsCurrent() == false)
				throw new ArgumentException("not current", "ctx");
		
			// Object name space test (and 'ctx' sanity checks)
			if (base.Exists(ctx) == false)
				return (false);

			return (Gl.IsProgram(ObjectName));
		}

		/// <summary>
		/// Create this ShaderProgram.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> used for creating this object.
		/// </param>
		public override void Create(RenderContext ctx)
		{
			// Create default compilation, but only if necessary
			if (mCompilationParams == null)
				mCompilationParams = new ShaderCompilerContext(ctx.ShadingVersion);
			// Base implementation
			base.Create(ctx);
		}

		/// <summary>
		/// Create a ShaderProgram name.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> used for creating this object name.
		/// </param>
		/// <returns>
		/// It returns a valid object name for this ShaderProgram.
		/// </returns>
		protected override uint CreateName(RenderContext ctx)
		{
			uint name;

			// Create program
			name = Gl.CreateProgram();
			RenderException.DebugCheckErrors();

			return (name);
		}

		/// <summary>
		/// Actually create this ShaderProgram resources.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> used for allocating resources.
		/// </param>
		protected override void CreateObject(RenderContext ctx)
		{
			if (IsLinked == true)
				throw new InvalidOperationException("already linked");

			// Link this shader program
			Link(ctx, mCompilationParams);
		}

		/// <summary>
		/// Delete a ShaderProgram name.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> used for deleting this object name.
		/// </param>
		/// <param name="name">
		/// A <see cref="System.UInt32"/> that specifies the object name to delete.
		/// </param>
		protected override void DeleteName(RenderContext ctx, uint name)
		{
			// Delete program
			Gl.DeleteProgram(name);
			RenderException.DebugCheckErrors();
		}

		/// <summary>
		/// ShaderCompilerContext used for compilation.
		/// </summary>
		private ShaderCompilerContext mCompilationParams;

		#endregion
	}
}
